package parser;
import java.io.IOException;
import java.util.ArrayList;

import scanner.Scanner;
import scanner.Token;
import scanner.TokenType;
import ast.ASTNode;
import ast.ASTTree;
import ast.nodes.BeginNode;
import ast.nodes.EndNode;
import ast.nodes.ForNode;
import ast.nodes.ParameterNode;
import ast.nodes.RegExNode;
import ast.nodes.SeriesNode;
import ast.nodes.WhileNode;
import ast.nodes.commandNodes.InsertNode;
import ast.nodes.commandNodes.PrintNode;
import ast.nodes.commandNodes.RemoveNode;
import ast.nodes.commandNodes.ReplaceNode;
import ast.nodes.commandNodes.SubstringNode;
import ast.nodes.regexNodes.CharacterNode;
import ast.nodes.regexNodes.OpNode;

public class Parser {

    private Token next;	// the last token read by scanner
    Scanner scan;
    boolean debug = false;
    int debugType = 1;
    ASTTree ast;
    
    ASTNode currentPartial;
    int whileErrorCount = 0;
    
    protected ArrayList<ParserError> errorList;

    public Parser(Scanner scan) {
        this.scan = scan;
        ast = new ASTTree();
        errorList = new ArrayList<ParserError>();

        //Read the start token
        try {
            next = scan.getToken();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        Debug("Beginning parse", 1);
        scan();
        Program();
    }

    private void Program() {
        Debug("In Program", 1);
        ASTNode rootNode = ast.getRoot();	// get the root node and pass it as a parent node
        Debug("root", 2);
        Begin(rootNode);
        Series(rootNode);
        End(rootNode);

        if(next != null)
        	saveError(next, "Expected end of program, but read in a value");
        else {
        	Debug("Successfully parsed!", 1);
        	ast.compress();
        }
    }

    private void Begin(ASTNode parent) {
        Debug("In Begin", 1);
        if ((next != null) && (next.getType() == TokenType.BEGIN)) {
            scan();
            BeginNode node = new BeginNode();
            currentPartial = node;
            Debug("Begin Node", 2);
            parent.addChild(node);
            ActionList(node);
        }
    }
    
    private void Series(ASTNode parent) {
        Debug("In Series", 1);
        if (next != null) {
        	if ((next.getType() != TokenType.END) && (next.getType() != TokenType.RIGHT_BRACE)) {
        		SeriesNode node = new SeriesNode();
        		currentPartial = node;
                parent.addChild(node);
                Debug("SeriesNode", 2);
                Exp(node);
                Series(node);
        	}
        }
    }

    private void End(ASTNode parent) {
        Debug("In End", 1);
        if ((next != null) && (next.getType() == TokenType.END)) {
            scan();
            EndNode node = new EndNode();
            currentPartial = node;
            parent.addChild(node);
            Debug("EndNode", 2);
            ActionList(node);
        }
    }
    
    private void Exp(ASTNode parent) {
        Debug("In Exp", 1);
        if (next.getType() == TokenType.LEFT_BRACE) {
            
        	// if the Series does not have a Regex create a regex token that runs it on everything
            MatchAllRegex(parent);
//        	Re(parent);
            
            ActionList(parent);
        }
        else if ((next.getType() == TokenType.FOR) || (next.getType() == TokenType.WHILE)) {
        	// if the Series is a For or While, go straight to ActionList
        	Command(parent);
        }
        else {
            Re(parent);
            ActionList(parent);
        }
    }
    
    private void ActionList(ASTNode parent) {
        Debug("In ActionList", 1);
        scan();
        Action(parent);
        // keep scanning until we find a right brace
        while((next != null) && (next.getType() != TokenType.RIGHT_BRACE)) {
        	if  (whileErrorCount < 1) {
        		saveError(next, "Expecting right brace");
        		whileErrorCount++;
        	}
            scan();
        }
        whileErrorCount = 0;
        scan();	// move to next character
    }

    private void Action(ASTNode parent) {
        Debug("In Action", 1);
        Command(parent);
        RestAction(parent);
    }

    private void RestAction(ASTNode parent) {
        Debug("In RestAction", 1);
        if (next.getType() == TokenType.SEMICOLON) {
            scan();
            Action(parent);
        }
    }

    private void Command(ASTNode parent) {
        Debug("In Command", 1);
        if (next.getType() == TokenType.SUBSTRING) {
            SubstringNode node = new SubstringNode(next);
            parent.addChild(node);
            Debug("SubstringNode", 2);
            scan();
            Substring(node);
        }
        else if (next.getType() == TokenType.REPLACE) {
            ReplaceNode node = new ReplaceNode(next);
            parent.addChild(node);
            Debug("ReplaceNode", 2);
            scan();
            Replace(node);
        }
        else if (next.getType() == TokenType.INSERT) {
            InsertNode node = new InsertNode(next);
            parent.addChild(node);
            Debug("InsertNode", 2);
            scan();
            Insert(node);
        }
        else if (next.getType() == TokenType.REMOVE) {
            RemoveNode node = new RemoveNode(next);
            parent.addChild(node);
            Debug("RemoveNode", 2);
            scan();
            Remove(node);
        }
        else if (next.getType() == TokenType.PRINT) {
            PrintNode node = new PrintNode(next);
            parent.addChild(node);
            Debug("PrintNode", 2);
            scan();
            Print(node);
        }
        else if (next.getType() == TokenType.FOR) {
            ForNode node = new ForNode(next);
            parent.addChild(node);
            Debug("ForNode", 2);
            scan(); //read token after for
            For(node);
        }
        else if (next.getType() == TokenType.WHILE) {
            WhileNode node = new WhileNode(next);
            parent.addChild(node);
            Debug("WhileNode", 2);
            scan();	//read token after while
            While(node);
        }
        else {
            saveError(next, "Expecting Command");
        }
    }
    
    // Begin Commands

    private void For(ASTNode parent) {
        Debug("In For", 1);
        //left parenthesis token
        if (next.getType() == TokenType.LEFT_PAREN) {
            scan();
            Num(parent);
            while ((next != null) && (next.getType() != TokenType.RIGHT_PAREN)) {
            	if  (whileErrorCount < 1) {
            		saveError(next, "Expecting Closing Parenthesis");
            		whileErrorCount++;
            	}
            	scan();
            }
            whileErrorCount = 0;
            scan();
        }
        if (next.getType() == TokenType.LEFT_BRACE) {
            scan();
            Series(parent);
            while ((next != null) && (next.getType() != TokenType.RIGHT_BRACE)) {
            	if  (whileErrorCount < 1) {
            		saveError(next, "Expecting Closing Brace");
            		whileErrorCount++;
            	}
            	scan();
            }
            whileErrorCount = 0;
            scan();
        }
    }

    private void While(ASTNode parent) {
        Debug("In While", 1);
        //left parenthesis token
        if (next.getType() == TokenType.LEFT_PAREN) {
            scan();
            Re(parent);
            while ((next != null) && (next.getType() != TokenType.RIGHT_PAREN)) {
            	if  (whileErrorCount < 1) {
            		saveError(next, "Expecting Closing Parenthesis");
            		whileErrorCount++;
            	}
            	scan();
            }
            whileErrorCount = 0;
            scan();
        }
        // Read until we get to the next left brace
        while (next.getType() != TokenType.LEFT_BRACE) {
        	if  (whileErrorCount < 1) {
        		saveError(next, "Expecting Closing Brace");
        		whileErrorCount++;
        	}
        	scan();
        }
        whileErrorCount = 0;
        // Once we get there, it will be read as a series
        scan();
        Series(parent);
        while ((next != null) && (next.getType() != TokenType.RIGHT_BRACE)) {
        	if  (whileErrorCount < 1) {
        		saveError(next, "Expecting Closing Brace");
        		whileErrorCount++;
        	}
        	scan();
        }
        whileErrorCount = 0;
        scan();
    }

    private void Substring(ASTNode parent) {
        Debug("In Substring", 1);

        if (next.getType() == TokenType.LEFT_PAREN) {
            scan();
            Num(parent);
            Y(parent);
        }
        while ((next != null) && (next.getType() != TokenType.RIGHT_PAREN)) {
        	if  (whileErrorCount < 1) {
        		saveError(next, "Expecting Closing Parenthesis");
        		whileErrorCount++;
        	}
        	scan();
        }
        whileErrorCount = 0;
        scan();
    }

    private void Replace(ASTNode parent) {
        Debug("In Replace", 1);
        if (next.getType() == TokenType.LEFT_PAREN) {
            scan();
            Char(parent);
            if (next.getType() == TokenType.COMMA) {
                scan();
                Char(parent);
            }
            while ((next != null) && (next.getType() != TokenType.RIGHT_PAREN)) {
            	if  (whileErrorCount < 1) {
            		saveError(next, "Expecting Closing Parenthesis");
            		whileErrorCount++;
            	}
            	scan();
            }
            whileErrorCount = 0;
            scan();
        }
    }

    private void Insert(ASTNode parent) {
        Debug("In Insert", 1);
        if (next.getType() == TokenType.LEFT_PAREN) {
            scan();
            if (next.getType() == TokenType.EOL) {
                EOL(parent);	//the first param of Insert is EOL
            }
            else
                Num(parent);
            if (next.getType() == TokenType.COMMA) {
                scan();
                Char(parent);
            }
            while ((next != null) && (next.getType() != TokenType.RIGHT_PAREN)) {
            	if  (whileErrorCount < 1) {
            		saveError(next, "Expecting Closing Parenthesis");
            		whileErrorCount++;
            	}
            	scan();
            }
            whileErrorCount = 0;
            scan();
        }
    }

    private void Print(ASTNode parent) {
        Debug("In Print", 1);
        if (next.getType() == TokenType.LEFT_PAREN) {
            scan();
            if (next.getType() == TokenType.LINE) {
                Line(parent);
            }
            else
                String(parent);
            while ((next != null) && (next.getType() != TokenType.RIGHT_PAREN)) {
            	if  (whileErrorCount < 1) {
            		saveError(next, "Expecting Closing Parenthesis");
            		whileErrorCount++;
            	}
            	scan();
            }
            whileErrorCount = 0;
            scan();
        }
    }

    private void Remove(ASTNode parent) {
        Debug("In Remove", 1);
        if (next.getType() == TokenType.LEFT_PAREN) {
            scan();
            Char(parent);
            while ((next != null) && (next.getType() == TokenType.RIGHT_PAREN)) {
            	if  (whileErrorCount < 1) {
            		saveError(next, "Expecting Closing Parenthesis");
            		whileErrorCount++;
            	}
            	scan();
            }
            whileErrorCount = 0;
            scan();
        }
    }
    
    // End Commands
    
    // Begin Terminals
    
    private void Y(ASTNode parent) {
        Debug("In Y", 1);
        scan(); // comma
        RestY(parent);
    }

    private void RestY(ASTNode parent) {
        Debug("In RestY", 1);
        if (next.getType() == TokenType.EOL) {
            EOL(parent);	//the second param in substring is EOL
        }
        else if(next.getType() == TokenType.INTEGER) {
            Num(parent);
        }
        else
        	saveError(next, "Expecting Integer, or EOL");
    }

    private void Num(ASTNode parent) {
        Debug("In Num", 1);
        Digit(parent);
        RestNum(parent);
    }

    private void RestNum(ASTNode parent) {
        Debug("In RestNum", 1);
        if (next.getType() == TokenType.INTEGER) {
            ParameterNode node = new ParameterNode(next);
            parent.addChild(node);
            Debug("ParameterNode", 2);
            scan();
            Num(parent);
        }
    }

    private void Digit(ASTNode parent) {
        Debug("In Digit", 1);
        if (next.getType() == TokenType.INTEGER) {
            ParameterNode node = new ParameterNode(next);
            parent.addChild(node);
            Debug("ParameterNode", 2);
            scan();
        }
    }

    private void Char(ASTNode parent) {
        Debug("In Char", 1);
        while (next.getType() == TokenType.CHARACTER) {
            ParameterNode node = new ParameterNode(next);
            parent.addChild(node);
            Debug("ParameterNode", 2);
            scan();
        }
    }

    private void EOL(ASTNode parent) {
        Debug("In EOL", 1);
        ParameterNode node = new ParameterNode(next);
        parent.addChild(node);
        Debug("ParameterNode", 2);
        scan();
    }

    private void Line(ASTNode parent) {
        Debug("In LINE", 1);
        ParameterNode node = new ParameterNode(next);
        parent.addChild(node);
        Debug("ParameterNode", 2);
        scan();
    }

    // String Terminal
    private void String(ASTNode parent) {
        Debug("In String", 1);
        // the token next is of type Quoted String
        ParameterNode node = new ParameterNode(next);
        parent.addChild(node);
        Debug("ParameterNode", 2);
        scan();
    }

    // End of Terminals
    
    // Begin RegEx
    
    private void Re(ASTNode parent) {
        Debug("In Re", 1);
        RegExNode node = new RegExNode(new Token("RegEx", TokenType.REGEX));
        parent.addChild(node);
        Debug("RegExNode", 2);
        
        L(node);
        
    }
    
    private void L(ASTNode parent) {
    	OpNode node = new OpNode(new Token("Concat", TokenType.CONCAT));
    	parent.addChild(node);
    	L1(node);
        if (next.getType() == TokenType.PIPE) {
        	L1_tail(node);
        }
//        else if ((next.getType() == TokenType.LEFT_PAREN) || (next.getTokenString().equals("a")) 
//        		|| (next.getTokenString().equals("b")) || (next.getTokenString().equals("c"))) {
        else if ((next.getType().equals(TokenType.LEFT_PAREN)) || (next.getType().equals(TokenType.CHARACTER))) {
        	L2_tail(node);
        }
    }
    
    private void L1(ASTNode parent) {
    	Debug("In L1", 1);
    	if (next.getType() == TokenType.LEFT_PAREN) {
    		scan();
    		L(parent);
    		while((next != null) && (next.getType() != TokenType.RIGHT_PAREN)) {
    			if  (whileErrorCount < 1) {
    				saveError(next, "Expecting Closing Parenthesis");
    				whileErrorCount++;
    			}
    			scan();
    		}
    		whileErrorCount = 0;
    		scan();
    		RestL1(parent);
    	}
    	else if (next.getTokenString().equals("a") || next.getTokenString().equals("b") || next.getTokenString().equals("c")) {
    		CharacterNode node = new CharacterNode(next);
    		parent.addChild(node);
    		Prim(node);
    		RestL1(parent);
    	}
    }
    
    private void RestL1(ASTNode parent) {
    	if ((next.getType() == TokenType.STAR) || (next.getType() == TokenType.PLUS) 
    			|| (next.getType() == TokenType.QUESTION_MARK)){
    		Op(parent);
    		L1(parent);
    	}
    }
    
    private void L1_tail(ASTNode parent) {
    	Debug("In L1-tail", 1);
    	if (next.getType() == TokenType.PIPE) {
//    		OpNode node = new OpNode(next);
//        	parent.addChild(node);
    		parent.setData(next);
        	scan();
        	
        	L(parent);
        	L1_tail(parent);
    	}
    }
    
    private void L2_tail(ASTNode parent) {
    	Debug("In L2-tail",1 );
    	if ((next.getType() == TokenType.LEFT_PAREN) || (next.getTokenString().equals("a")) 
        		|| (next.getTokenString().equals("b")) || (next.getTokenString().equals("c"))) {
    		L(parent);
    		L2_tail(parent);
    	}
    }
    
    private void Prim(ASTNode parent) {
    	Debug("In Prim", 1);
//    	CharacterNode node = new CharacterNode(next);
//    	parent.addChild(node);
    	parent.setData(next);
    	scan();
    }
    
    private void Op(ASTNode parent) {
    	Debug("In Op", 1);
//    	OpNode node = new OpNode(next);
//    	parent.addChild(node);
    	parent.setData(next);
    	scan();
    }
    
//    Moved to AST
//    private void Compress(ASTNode currentNode) {
//    	if ((currentNode.getData() != null) && currentNode.getData().getType().equals(TokenType.CONCAT) && (currentNode.getChildren().size() == 1)) {
//    		currentNode.setData(currentNode.getChildren().get(0).getData());
//    		currentNode.setChildren(currentNode.getChildren().get(0).getChildren());
//    	}
//    	else
//    		for (ASTNode child : currentNode.getChildren())
//    			Compress(child);
//    }
    
    private void MatchAllRegex(ASTNode parent) {
    	//manually create RegEx to include all matches (a|b|c)*
    	Debug("In AllRegex", 1);
        RegExNode regExNode = new RegExNode();
        parent.addChild(regExNode);
                
        OpNode starNode1 = new OpNode(new Token("*", TokenType.STAR));
        regExNode.addChild(starNode1);
        
        OpNode pipeNode1 = new OpNode(new Token ("|", TokenType.PIPE));
        starNode1.addChild(pipeNode1);
        
        CharacterNode charA = new CharacterNode(new Token("a", TokenType.CHARACTER));
        pipeNode1.addChild(charA);
        OpNode pipeNode2 = new OpNode(new Token("|", TokenType.PIPE));
        pipeNode1.addChild(pipeNode2);
        
        CharacterNode charB = new CharacterNode(new Token("b", TokenType.CHARACTER));
        pipeNode2.addChild(charB);
        CharacterNode charC = new CharacterNode(new Token("c", TokenType.CHARACTER));
        pipeNode2.addChild(charC);
    }
    // End RegEx
    
    
    private void saveError(Token t, String desc) {
    	ASTTree tree = new ASTTree();
    	tree.setRoot(currentPartial);
    	ParserError err = new ParserError(t, tree, desc);
    	errorList.add(err);
    }

    private void scan() {
        Debug("In scan", 1);
        if (next != null) {
	        try {
	            next = scan.getToken();
	            while((next != null) && (next.getType().equals(TokenType.ERROR))) {
	            	saveError(next, "Invalid symbol");
	            	next = scan.getToken();
	            }
	        } catch (IOException e) {
	            // TODO Auto-generated catch block
	            e.printStackTrace();
	        }
        }
    }
    
    public void Debug(String msg, int type) {
        if (debug && (type==debugType))
            System.out.println(msg);
    }

    public ASTTree getASTTree() {
        return ast;
    }

    public void setDebug(boolean value) {
        debug = value;
    }

    public void setDebugType(int type) {
        debugType = type;
    }
    
    public ArrayList<ParserError> getErrorList() {
        return errorList;
    }
}
